﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NDesk.Options;
using System.Reflection;
using System.Threading;

class CommonOptions
{
    static readonly string _assemblyFullName =
        Assembly.GetExecutingAssembly().GetName().FullName;
    static readonly string _assemblyName =
        Assembly.GetExecutingAssembly().GetName().Name;

    OptionSet _optionSet = new OptionSet();
    bool hasRun = false;
    object syncLock = new object();

    public void ParseOptions(string[] args,
                               out List<string> floatingArgs)
    {
        ParseOptions(args, out floatingArgs, -1, -1);
    }

    public bool ParseOptions(string[] args,
                               out List<string> floatingArgs,
                               int minFloatingArgs,
                               int maxFloatingArgs)
    {
        lock (syncLock)
        {
            // This method is only allowed to be called once
            // because it adds an item to the options list, which
            // pretty much breaks the entire class from being used
            // more than once.  To fix, handle the case where this
            // method is called multiple times, and the case where
            // options are added after this method has already been
            // called once.
            if (hasRun)
            {
                hasRun = true;
                throw new InvalidOperationException(
                        "Method can be called only once.");
            }
        }

        bool showHelp = false;

        _optionSet.Add(
                "h|?|help",
                "Show this help.",
                v => showHelp = v != null);

        try
        {
            floatingArgs = _optionSet.Parse(args);
        }
        catch (OptionException ex)
        {
            WriteOptionsErrorMessage(ex.Message);
            floatingArgs = null;
            return false;
        }

        if (showHelp)
        {
            WriteUsageInformation();
            return false;
        }

        if ((minFloatingArgs != -1 && floatingArgs.Count < minFloatingArgs) ||
            (maxFloatingArgs != -1 && floatingArgs.Count > maxFloatingArgs))
        {
            WriteOptionsErrorMessage("Wrong number of arguments.");
            return false;
        }

        return true;
    }

    public void AddOption(string prototype,
                          string description,
                          Action<string> action)
    {
        _optionSet.Add(prototype, description, action);
    }

    public void AddOption<T>(string prototype,
                             string description,
                             Action<T> action)
    {
        _optionSet.Add<T>(prototype, description, action);
    }

    public void WriteUsageInformation()
    {
        Console.WriteLine(_assemblyFullName);
        Console.WriteLine("Usage: " + _assemblyName
            + " [OPTIONS] <database_path>");

        _optionSet.WriteOptionDescriptions(Console.Out);
        Console.WriteLine("Examples:");

        Console.WriteLine("  " + _assemblyName
            + " -t4 -w KeePassDb.kdbx");

        Console.WriteLine("  john --incremental --stdout | "
            + _assemblyName + " -w - KeePassDb.kdbx");
    }

    public void WriteOptionsErrorMessage(string message)
    {
        Console.WriteLine(_assemblyFullName);
        if (message != null)
            Console.WriteLine(message);

        Console.WriteLine("Try '"
            + Assembly.GetExecutingAssembly().GetName().Name
            + " --help' for more information.");
    }
}
